﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Kokomo.PeInspector.Reflection.Disassembly;
using System.Reflection;
using System.Threading;
using Kokomo.Mirror.CliMetadata;
using System.Threading.Tasks;
using System.Runtime.CompilerServices;

namespace Kokomo.PeInspector.Reflection
{
	public abstract class DependencyIndex
	{
		protected DependencyIndex(
			IAssemblyListHost host
			)
		{
			if (host == null) throw new ArgumentNullException("host");

			this.Host = host;

			this._dependencyIndexCompleteEvent = new ManualResetEvent(true);
		}

		public IAssemblyListHost Host { get; private set; }

		public abstract IEnumerable<MsilDependency> GetDependenciesOf(
			ModuleMemberReference dependentMember,
			ProgressInfo progress
			);
		public abstract IEnumerable<IGrouping<ModuleMemberReference, MsilMemberDependency>> GetDependentsOf(
			ModuleMemberReference dependencyMember,
			ProgressInfo progress
			);

		public void AddAssembly(Assembly assembly)
		{
			if (assembly == null) throw new ArgumentNullException("assembly");

			this._dependencyIndexCompleteEvent.Reset();
			Interlocked.Increment(ref this._buildingCount);

			Task indexerTask = new Task(this.OnAddAssemblyAsync, assembly);
			indexerTask.Start();
		}

		private ManualResetEvent _dependencyIndexCompleteEvent;
		private int _buildingCount;
		public void WaitForIndex()
		{
			this.WaitForIndex(TimeSpan.FromMilliseconds(-1));
		}
		public bool WaitForIndex(TimeSpan timeout)
		{
			return this._dependencyIndexCompleteEvent.WaitOne(timeout);
		}

		private void OnAddAssemblyAsync(object arg)
		{
			Assembly assembly = (Assembly)arg;

			ThreadPriority priority = Thread.CurrentThread.Priority;
			Thread.CurrentThread.Priority = ThreadPriority.BelowNormal;

			try
			{
				this.AddDependenciesToIndex(assembly);
			}
			catch { }

			if (Interlocked.Decrement(ref this._buildingCount) == 0)
				this._dependencyIndexCompleteEvent.Set();

			Thread.CurrentThread.Priority = priority;
		}

		protected abstract void AddDependenciesToIndex(
			Assembly assembly
		);

		protected IEnumerable<IGrouping<ModuleMemberReference, MsilDependency>> CalculateDependencies(
			Assembly assembly
			)
		{
			AssemblyIndex index = this.Host.GetAssemblyIndex(assembly);

			foreach (var type in index.GetAllTypes())
			{
				IEnumerable<MsilDependency> typeDependencies = this.CalculateTypeDependencies(type);
				yield return new Grouping<ModuleMemberReference, MsilDependency>(
					new ModuleMemberReference(type),
					typeDependencies
					);

				//Type[] interfaceTypes = type.GetInterfaces();
				//InterfaceMapping[] interfaceMappings = new InterfaceMapping[interfaceTypes.Length];
				//for (int i = 0; i < interfaceTypes.Length; i++)
				//{
				//    InterfaceMapping mapping = type.GetInterfaceMap(interfaceTypes[i]);
				//    interfaceMappings[i] = mapping;
				//}
				//ILookup<ModuleMemberReference, ModuleMemberReference> interfaceLookup = interfaceMappings.SelectMany(InterfaceMethodMapping.FromInterfaceMapping).ToLookup(
				//    m => new ModuleMemberReference(m.Implementation),
				//    m => new ModuleMemberReference(m.InterfaceMethod)
				//    );

				ConstructorInfo[] constructors = type.GetConstructors(BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				foreach (var method in constructors)
				{
					IEnumerable<MsilDependency> dependencies = this.CalculateMethodDependencies(method);
					yield return new Grouping<ModuleMemberReference, MsilDependency>(
						new ModuleMemberReference(method),
						dependencies
						);
				}

				MethodInfo[] methods = type.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.DeclaredOnly);
				foreach (var method in methods)
				{
					IList<MsilDependency> dependencies = this.CalculateMethodDependencies(method);
					//ModuleMemberReference[] interfaceMethods = interfaceLookup[new ModuleMemberReference(method)].ToArray();
					//foreach (var interfaceMethod in interfaceMethods)
					//{
					//    dependencies.Add(MsilMethodDependency.FromMethodRef(interfaceMethod, MsilMethodUsage.Implements));
					//}

					yield return new Grouping<ModuleMemberReference, MsilDependency>(
						new ModuleMemberReference(method),
						dependencies
						);
				}
			}

			IExtendedAssemblyInfo extendedAssembly = assembly as IExtendedAssemblyInfo;
			if (extendedAssembly != null)
				extendedAssembly.Unload();
		}

		protected IEnumerable<MsilDependency> CalculateMemberDependencies(MemberInfo member)
		{
			if (member == null) throw new ArgumentNullException("member");

			MethodBase method = member as MethodBase;
			if (method != null)
			{
				return this.CalculateMethodDependencies(method);
			}
			else
			{
				Type type = member as Type;
				if (type != null)
				{
					return this.CalculateTypeDependencies(type);
				}
				else
				{
					return new MsilDependency[0];
				}
			}
		}
		protected IEnumerable<MsilDependency> CalculateTypeDependencies(Type type)
		{
			if (type == null) throw new ArgumentNullException("type");

			List<MsilDependency> dependencies = new List<MsilDependency>();
			try
			{
				Type baseType = type.BaseType;
				if (baseType != null)
					dependencies.Add(MsilTypeDependency.FromType(baseType, MsilTypeUsage.Extends));
			}
			catch { }

			try
			{
				IList<CustomAttributeData> attributes = type.GetCustomAttributesData();
				foreach (var attribute in attributes)
				{
					dependencies.Add(MsilMethodDependency.FromMethod(attribute.Constructor, MsilMethodUsage.Invoke));
					dependencies.Add(MsilTypeDependency.FromType(attribute.Constructor.DeclaringType, MsilTypeUsage.Attribute));
				}
			}
			catch { }

			try
			{
				Type[] interfaces = type.GetInterfaces();
				foreach (var itf in interfaces)
				{
					dependencies.Add(MsilTypeDependency.FromType(itf, MsilTypeUsage.Implements));
				}
			}
			catch { }

			return dependencies;
		}
		protected IList<MsilDependency> CalculateMethodDependencies(
			MethodBase method
			)
		{
			if (method == null) throw new ArgumentNullException("method");

			List<MsilDependency> dependencies = new List<MsilDependency>();
			try
			{
				IList<CustomAttributeData> attributes = method.GetCustomAttributesData();
				foreach (var attribute in attributes)
				{
					dependencies.Add(MsilMethodDependency.FromMethod(attribute.Constructor, MsilMethodUsage.Invoke));
					dependencies.Add(MsilTypeDependency.FromType(attribute.Constructor.DeclaringType, MsilTypeUsage.Attribute));
				}
				if (method.IsDefined(typeof(ExtensionAttribute), true))
				{
					ParameterInfo parameter = method.GetParameters().FirstOrDefault();
					if (parameter != null)
					{
						Type extendedType = parameter.ParameterType;
						dependencies.Add(MsilTypeDependency.FromType(extendedType, MsilTypeUsage.Extension));
					}
				}
				dependencies.AddRange(MsilDecompiler.GetDependenciesOf(method).Where(IsCanonicalDependency));
			}
			catch
			{
				return new MsilDependency[0];
			}

			return dependencies;
		}

		private static bool IsCanonicalDependency(MsilDependency dependency)
		{
			MsilMemberDependency memberDependency = dependency as MsilMemberDependency;
			bool isCanon =
				(memberDependency == null)
				|| (
					!CodedIndex.IsReferenceToken(memberDependency.MetadataToken)
					&& !CodedIndex.IsGenericParamToken(memberDependency.MetadataToken)
					);
			return isCanon;
		}
	}
}
